package dbsucxentW.constructW;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Enumeration;
import java.util.Vector;
import javax.swing.tree.DefaultMutableTreeNode;

/**
 * @author Lee Tzyy Ming
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
// Reconstruction for Sucxent++
// JVM parameters -Xmx1300m, -XX:+AggressiveHeap doesn't seems to work 
public class Reconstruct {

    ResultSet _results, _longResult;
    String _unsplitPaths[];
    String _paths[][];
    boolean _pathIsAttribute[];
    Vector _attribute = new Vector();
    //Vector _atttributeValue = new Vector();
    long curAncestorDeweyGroup, oldAncestorDeweyGroup;
    long[] ancestorValues;
    Connection _dbConnection;
    FileOutputStream fileOutputStream;
    StringBuilder stringResult;
    DefaultMutableTreeNode top;
    DefaultMutableTreeNode current;
    public final int TREE = 1,  TEXT = 2,  FILE = 3;
    int _output = 0, _queryDepth, _pathID, _oldPathID;
    long currentRow = 0;
    long endRow = 0;
    long totalRow = 0;
    //klarinda 20060817 what does nodeCount used for? 
    long nodeCount = 0;
    boolean overflow = false;
    //int _level = 0;
    //Timer timer;
    String _queryLastElement, _startElement, rootName = "results";
    private int stringSize = 0,  intStringSize = 0;
    private String dataSrc;

    // initialize the reconstruction with resultset result, connection dbConnection, the queryDepth
    //	the current row to start recconstruction from and the end row where the reconstruction will stop here
    public Reconstruct(ResultSet results, Connection dbConnection, String queryLastElement, int queryDepth, long current, long end) {
        _results = results;
        _dbConnection = dbConnection;
        _queryLastElement = queryLastElement;
        _queryDepth = queryDepth;
        currentRow = current;
        endRow = end;
    }

    public Reconstruct(ResultSet results, Connection dbConnection, String queryLastElement, 
            int queryDepth, long current, long end, String dataSrc) {
        this.dataSrc = dataSrc;
        _results = results;
        _dbConnection = dbConnection;
        _queryLastElement = queryLastElement;
        _queryDepth = queryDepth;
        currentRow = current;
        endRow = end;
    }
    
    // reconstruct and output the result to a file
    public void reconstructFile(String filename) throws FileNotFoundException, SQLException, IOException {
        _output = FILE;

        if (filename.trim().equals("")) {
            filename = "queryResults";
        }
        File file = new File(filename + ".xml");
        fileOutputStream = new FileOutputStream(file);

        fileOutputStream.write(("<" + rootName + ">").getBytes());
        reconstruct();
        fileOutputStream.write(("</" + rootName + ">").getBytes());

        fileOutputStream.close();

        openFile(filename);
    }

    // reconstruct and output the result as XML text stored in a string
    public String reconstructText() throws FileNotFoundException, SQLException, IOException {
        //timer = new Timer();
        stringResult = new StringBuilder();
        _output = TEXT;
        stringResult.append("<" + rootName + ">");
        reconstruct();
        stringResult.append("</" + rootName + ">");

        //System.out.println("Reconstruct.reconstructText(), stringResult: " + stringResult);	//klarinda 20060804
        return stringResult.toString();
    }

    // reconstruct and output the result as a tree
    public DefaultMutableTreeNode reconstructTree() throws FileNotFoundException, SQLException, IOException {
        //timer = new Timer();
        _output = TREE;
        DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(rootName);
        current = rootNode;
        reconstruct();
        
        DefaultMutableTreeNode newRoot = new DefaultMutableTreeNode(rootName);
        DefaultMutableTreeNode temp = (DefaultMutableTreeNode) rootNode.getFirstChild();
        
        for(Enumeration em = temp.children(); em.hasMoreElements();){
            newRoot.add((DefaultMutableTreeNode)em.nextElement());
        }
        return newRoot;
    }

    // given the pathID, this method will return the elements in the path in an array of strings
    private String[] getPath(int pathID) {
        if (_paths[pathID - 1] == null) {
            setPath(pathID);
        }
        return _paths[pathID - 1];
    }

    // check if this pathID is the path to an attribute
    private boolean isPathAttribute(int pathID) {
        if (_paths[pathID - 1] == null) {
            setPath(pathID);
        }
        return _pathIsAttribute[pathID - 1];
    }

    // given a pathId, split the path and obtain useful information from it, only obtain path information when needed
    private void setPath(int pathID) {
        // split the path into its path elements
        _paths[pathID - 1] = _unsplitPaths[pathID - 1].split("\\.");
        int i = 1;
        _paths[pathID - 1][_paths[pathID - 1].length - 1] = _paths[pathID - 1][_paths[pathID - 1].length - 1].trim();
        // remove the # at end of each path element
        for (i = 1; i < _paths[pathID - 1].length; i++) {
            _paths[pathID - 1][i] = _paths[pathID - 1][i].substring(0, (_paths[pathID - 1][i]).length() - 1);
        }
        char lastElement = _paths[pathID - 1][i - 1].charAt(0);
        // if @ is found at starting of last path element, path is an attribute and set the attribute array
        if (lastElement == '@') {
            _pathIsAttribute[pathID - 1] = true;
        } else {
            _pathIsAttribute[pathID - 1] = false;
        }
    }

    // start the reconstruction process
    public void reconstruct() throws FileNotFoundException, SQLException, IOException {
        int newBranchOrder;
        ancestorValues = getAncestorValues(_dbConnection);
        _unsplitPaths = getPaths(_dbConnection);
        _paths = new String[_unsplitPaths.length][];
        _pathIsAttribute = new boolean[_unsplitPaths.length];


        long maxMem = (Runtime.getRuntime().maxMemory() / 10) * 5;	// set the max memory to use before throw out of memory exception

        // obtain the total number of rows (also the estimated number of tuples in the interface)
        _results.last();
        totalRow = _results.getRow();

        // go to the first node to be constructed, either from row 0 or from a specify location currentRow
        if (currentRow == 0) {
            _results.first();
        } else {
            _results.absolute((int) currentRow);
        }

        try {
            _pathID = _results.getInt("PathId");
        } catch (SQLException e) {
            writeLeaf("No results");
            return;
        }
        
        int tempBranchOrder = _results.getInt("BranchOrder");

        // write out the paths from queryDepth to the first leaf node
        int j;
        for (j = _queryDepth; j < getPath(_pathID).length - 1; j++) {
            startElement(getPath(_pathID)[j]);
            if (tempBranchOrder == j && stringResult != null) {
                intStringSize = stringResult.length();
            }
        }

        oldAncestorDeweyGroup = _results.getLong("AncestorDeweyGroup");
        _oldPathID = _pathID;
        String startValue = _results.getString("LeafValue");

        //System.out.println("Reconstruct.reconstruct() before if(isPathAttribute)");			//klarinda 20060806

        if (isPathAttribute(_pathID) == false) {
            //System.out.println("Reconstruct.reconstruct() isAttr = false, _pathID: " + _pathID + ", j: " + j);		//klarinda 20060806
            //System.out.println("Reconstruct.reconstruct() getPath(_pathID).length: " + getPath(_pathID).length);	//klarinda 20060806
            if (getPath(_pathID).length > j) //klarinda 20060804 - add if condition to make sure no array out of bound exception (if the 1st result is text w/o parent element)
            {
                startElement(getPath(_pathID)[j]);
            }
            //System.out.println("Reconstruct.reconstruct() after startElement");			//klarinda 20060806

            if (startValue != null) {
                writeLeaf(startValue.trim());
            } else {
                writeLeaf(" ");
            }

        } else {
            //System.out.println("Reconstruct.reconstruct() isAttr = true, _pathID: " + _pathID + ", j: " + j);		//klarinda 20060817
            String attributeName = getPath(_pathID)[j];
            attributeName = attributeName.substring(1, attributeName.length());
            if (startValue != null) {
                writeAttribute(attributeName + "=" + startValue.trim());
            } else {
                writeAttribute(attributeName);
            }
        }
        //System.out.println("Reconstruct.reconstruct() after if(isPathAttribute)");			//klarinda 20060817
        currentRow++;

        // loop through all nodes until we reach the last node
        while (_results.next()) {
            _pathID = _results.getInt("PathId");

            curAncestorDeweyGroup = _results.getLong("AncestorDeweyGroup");

            // find the nearest common ancestor using theorem 3
            for (newBranchOrder = _results.getInt("BranchOrder") - 2; newBranchOrder >= 0; newBranchOrder--) {
                //System.out.println("Reconstruct.reconstruct() _rValue[" + newBranchOrder + "]: " + _rValue[newBranchOrder]);	//klarinda 20060817
                //klarinda 20060817, should be <, not <= if (deweyDiff <= _rValue[newBranchOrder]) break; 
                if (curAncestorDeweyGroup / ancestorValues[newBranchOrder] == oldAncestorDeweyGroup / ancestorValues[newBranchOrder]) {
                    break;
                }			//klarinda 20060817
            }
            newBranchOrder += 2;
            //System.out.println("Reconstruct.reconstruct() newBranchOrder:" + newBranchOrder);	//klarinda 20060817
            if (newBranchOrder < _queryDepth - 1) {
                newBranchOrder = _queryDepth - 1;
            }
            //System.out.println("Reconstruct.reconstruct() newBranchOrder:" + newBranchOrder);	//klarinda 20060817

            int e = (getPath(_oldPathID).length) - 1;
            if (isPathAttribute(_oldPathID) == true) {
                e--;
                newBranchOrder = e;//klarinda 20061109 (because separate attribute table)
            }
            // close path till the common ancestor
            for (; e > newBranchOrder; e--) {
                endElement(getPath(_oldPathID)[e]);
            }

            String value = _results.getString("LeafValue");
            int s = newBranchOrder + 1;
            // open path from common ancestor to second node
            for (; s < getPath(_pathID).length - 1; s++) {
                startElement(getPath(_pathID)[s]);
            }
            // write out the leaf value
            if (isPathAttribute(_pathID) == false) {
                if (s < getPath(_pathID).length) {
                    startElement(getPath(_pathID)[s]);
                }
                if (value != null) {
                    /*if (value.substring(0,2).equals("@@")) {		// used varchar(max) instead, hence ignoring this
                    _longResult.next();
                    if(_deweyOrderSum == _longResult.getLong(2)){
                    writeLeaf(_longResult.getString(1).trim());
                    } else {
                    writeLeaf(value.trim());
                    _longResult.previous();
                    }
                    }
                    else*/
                    writeLeaf(value.trim());
                } else {
                    writeLeaf(" ");
                }
            } else {
                String attributeName = getPath(_pathID)[s];
                attributeName = attributeName.substring(1, attributeName.length());
                if (value != null) {
                    writeAttribute(attributeName + "=" + value.trim());
                } else {
                    writeAttribute(attributeName);
                }
            }

            // replace first node with second node and move on to the next node
            oldAncestorDeweyGroup = curAncestorDeweyGroup;
            _oldPathID = _pathID;

            currentRow++;

            //long freeMem = Runtime.getRuntime().freeMemory();
            //System.out.println("Free Memory: "+ freeMem + " EndRow: " + endRow + " Current: " + currentRow);
            // if used memory exceed the quota set, stop reconstruction (need to leave some memory for GUI display)
            if (endRow == 0 && (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) > maxMem) {
                //System.out.println("Memory OverFlow!");
                overflow = true;
                break;
            /*
            // set each batch of reconstruction to reconstruct the same number of rows
            } else if (endRow == 0 &&  currentRow%2000000 == 0) {
            System.out.println("Memory OverFlow!");
            overflow = true;
            break;
             */
            // reconstruct within the range of rows specified	
            } else if (endRow != 0 && currentRow == endRow) {
                overflow = true;
                break;
            }

        //long endTime = System.nanoTime()/1000000 - startTime;
        //System.out.println("row time:" + endTime);
        //startTime = System.nanoTime()/1000000;

        }
        //System.out.println("Total memory: " + Runtime.getRuntime().totalMemory());
        //System.out.println("Free memory: " + Runtime.getRuntime().freeMemory());
        long temp = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
        //System.out.println("Memory used: " + temp);


        if (overflow) {
            _results.next();
            if (_results.getInt(3) > _queryDepth) {
                nodeCount = nodeCount - _results.getInt("BranchOrder") + _queryDepth - 1;
            }
        }

        //System.out.println("\nNumber of tuples processes: " + currentRow);
        //System.out.println("\nNumber of nodes processed: " + nodeCount);

        //boolean sizeNull = true;

        // last node, therefore close path till queryDepth
        if (isPathAttribute(_oldPathID) == false) {
            for (int i = getPath(_oldPathID).length - 1; i >= _queryDepth; i--) {
                endElement(getPath(_oldPathID)[i]);
            //if (overflow && sizeNull && i <= _results.getInt(3)) {stringSize = stringResult.length();}
            //if (overflow) nodeCount--;
            }
        } else {
            for (int i = getPath(_oldPathID).length - 2; i >= _queryDepth; i--) {
                endElement(getPath(_oldPathID)[i]);
            //if (overflow) nodeCount--;
            }
        }
    }

    // open an XML file
    public static void openFile(String filename) throws IOException {
        Runtime rt = Runtime.getRuntime();

        String[] cmd = new String[4];
        cmd[0] = "cmd.exe";
        cmd[1] = "/C";
        cmd[2] = "start";
        cmd[3] = filename + ".xml";

        Process p = rt.exec(cmd);
    }

    // write an attribute, stores attribute in a vector an delay its write
    private void writeAttribute(String attribute) {
        nodeCount++;
        _attribute.add(attribute);
    }

    // declare a start element, the writing of start element is delay by 1
    private void startElement(String element) throws IOException {
        nodeCount++;
        writeStartElement();
        _startElement = element;
    }

    // write the previous stored start element and its corresponding attribute if any
    // writing in 3 different types of output depending on which output the user had choosen
    private void writeStartElement() throws IOException {
        //System.out.println("Reconstruct.writeStartElement");			//klarinda 20061110
        //if (element.trim().equals("")) return;
        String attribute;
        if (_startElement == null) {
            return;
        }
        if (_output == FILE) {
            fileOutputStream.write('<');
            fileOutputStream.write(_startElement.getBytes());
            for (int i = 0; i < _attribute.size(); i++) {
                attribute = "  " + (String) _attribute.elementAt(i);
                fileOutputStream.write(attribute.getBytes());
            }
            fileOutputStream.write('>');
        } else if (_output == TEXT) {
            //for (int i=0; i < _level; i++)
            //	stringResult.append("      ");

            stringResult.append("<" + _startElement);
            for (int i = 0; i < _attribute.size(); i++) {
                stringResult.append("  " + (String) _attribute.elementAt(i));
            }

            stringResult.append(">");
        //stringResult.append(">\n");
        //_level++;
        } else {	// (output == TREE)
//            for (int i = 0; i < _attribute.size(); i++) {
//                _startElement += "  " + (String) _attribute.elementAt(i);
//            }

            DefaultMutableTreeNode node = new DefaultMutableTreeNode(_startElement);
            
            for(Object att : _attribute){
                String valuePair = att.toString();
                String[] pair = valuePair.split("=");
                
                DefaultMutableTreeNode attNode = 
                        new DefaultMutableTreeNode("@" + pair[0]);
                if(pair.length == 2){
                    attNode.add(new DefaultMutableTreeNode(pair[1]));
                }
                node.add(attNode);
            }
            current.add(node);
            current = node;
        }
        _attribute.clear();
        _startElement = null;
    }

    // declare an end element and write out this element immediately
    private void endElement(String element) throws IOException {
        //System.out.println("Reconstruct.endElement(" + element + ")");			//klarinda 20061110
        //if (element.trim().equals("")) return;
        writeStartElement();
        if (_output == FILE) {
            fileOutputStream.write('<');
            fileOutputStream.write('/');
            fileOutputStream.write(element.getBytes());
            fileOutputStream.write('>');
        } else if (_output == TEXT) {
            //_level--;
            //for (int i=0; i < _level; i++)
            //	stringResult.append("      ");

            stringResult.append("</" + element + ">");
        //stringResult.append("</" + element + ">\n");
        } else {	// (output == TREE)
            current = (DefaultMutableTreeNode) current.getParent();
        }
    }

    // write the leaf value
    private void writeLeaf(String leaf) throws IOException {
        writeStartElement();
        if (_output == FILE) {
            fileOutputStream.write(leaf.getBytes());
        } else if (_output == TEXT) {
            stringResult.append(leaf);
        } else {// (output == TREE)
            if (leaf != null && !"".equals(leaf.trim())) {
                DefaultMutableTreeNode node = new DefaultMutableTreeNode(leaf);
                current.add(node);
            }
        }
    }

    // used previously on SQL server2000, with a separate table to store long data, SQL 2005 uses max type which is faster
    public ResultSet getLongResult(Connection dbConnection) throws SQLException {
        int docId = 1;
        //System.out.println("Reconstruct long text result open");
        //Statement statement = _dbConnection.createStatement (ResultSet.TYPE_SCROLL_INSENSITIVE,
        //        				ResultSet.CONCUR_READ_ONLY); // using CONCUR_READ_ONLY will hang the sql
        Statement statement = _dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                ResultSet.CONCUR_UPDATABLE);
        //klarinda 20061006 change ModOrdSum to DeweyOrdSum
        ResultSet results = statement.executeQuery("SELECT LeafValue, DeweyOrderSum FROM TextContent WHERE DocId=" + docId);

        return results;
    }

    // get the list og RValues in an array
    public long[] getAncestorValues(Connection dbConnection) throws SQLException {
        int docId = 1;
        long[] ret;
        Statement statement = _dbConnection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
                ResultSet.CONCUR_UPDATABLE);
        String dataSrcPre = (this.dataSrc == null)?"":dataSrc + "_";
        ResultSet results = statement.executeQuery(
                "SELECT AncestorValue FROM " + dataSrcPre + "DocumentRValue WHERE DocId = " + docId);

        results.last();
        ret = new long[results.getRow()];
        results.beforeFirst();

        for (int i = 0; results.next(); i++) {
            ret[i] = results.getLong(1);
        }

        results.close();
        statement.close();
        results = null;
        statement = null;
        return ret;
    }

    // get all the paths and return it in an array of string
    public String[] getPaths(Connection dbConnection) throws SQLException {
        String[] unsplitPaths;

        //System.out.println("Reconstruct get paths open");
        if (_dbConnection.getAutoCommit()) {
            //System.out.println("auto commit on");
        }
        _dbConnection.commit();
        Statement statement = _dbConnection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
                ResultSet.CONCUR_READ_ONLY); 	// ms sql server 2005
        //	Statement statement = _dbConnection.createStatement (ResultSet.TYPE_SCROLL_INSENSITIVE,
        //			ResultSet.CONCUR_UPDATABLE);				// ms sql server 2000
        //System.out.println("Reconstruct get paths before resultset");
        String dataSrcPre = (this.dataSrc == null)?"":dataSrc + "_";
        ResultSet results = statement.executeQuery(
                "SELECT PathExp FROM " + dataSrcPre + "Path ORDER BY PathID");
        //System.out.println("Reconstruct get paths after resultset");

        results.last();
        unsplitPaths = new String[results.getRow()];
        results.beforeFirst();

        for (int i = 0; results.next(); i++) {
            unsplitPaths[i] = results.getString(1);
        }

        results.close();
        statement.close();
        results = null;
        statement = null;
        //System.out.println("Reconstruct get paths close");

        return unsplitPaths;
    }

    public long getRow() {
        return currentRow;
    }

    public long getTotalRows() {
        return totalRow;
    }

    public boolean isOverflow() {
        return overflow;
    }

    public int getSize() {
        return stringSize;
    }
}
